package com.sinosoft.bee.services.provider.demo.aop;

import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.RedisStringCommands;
import org.springframework.data.redis.connection.jedis.JedisConverters;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.data.redis.core.types.Expiration;
import org.springframework.stereotype.Component;
import redis.clients.jedis.JedisCluster;

import javax.annotation.Resource;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;


@Slf4j
public class RedisDistributeLock implements DistributeLock {

    private final String CHARSET_NAME = "UTF-8";
    /**
     * 单位:秒
     */
    private final Integer EXPIRE_TIME = 5;
    private Long timestamp;
    private RedisTemplate<String, Object> redisTemplate;
    private String lockname;
    private Object value;
    private ExecutorService executorService;
    private boolean isDaemon = true;

    private final Integer sleepTime = 1;


    public RedisDistributeLock(RedisTemplate redisTemplate, String lockname, Object value) {

        this.lockname = lockname;
        this.value = value;
        this.redisTemplate = redisTemplate;
    }




    @Override
    public boolean lock() {

        //获取锁，成功返回true
        return redisTemplate.execute((RedisCallback<Boolean>) connection -> {
            String result = "";
            JedisCluster cluster = (JedisCluster) connection.getNativeConnection();
            byte[] nxxx = JedisConverters.toSetCommandNxXxArgument(RedisStringCommands.SetOption.SET_IF_ABSENT);
            byte[] expx = JedisConverters.toSetCommandExPxArgument(Expiration.seconds(EXPIRE_TIME));

            try {
                timestamp = System.currentTimeMillis();
                //初始化守护线程
                initDaemon();
                result = cluster.set(lockname.getBytes("utf-8"), value.toString().getBytes("utf-8"),
                        nxxx, expx, Expiration.seconds(EXPIRE_TIME).getExpirationTime());
            } catch (Exception ex) {
                log.error("redis分布式锁获取异常：{}", ex);
            }
            if ("OK".equalsIgnoreCase(result)) {
                return true;
            } else {
                return false;
            }

        });


    }

    @Override
    public boolean unlock() {
        //修改守护标志
        isDaemon = false;
        //获取锁，成功返回true
        Long result = redisTemplate.execute((RedisCallback<Long>) connection -> {
            JedisCluster cluster = (JedisCluster) connection.getNativeConnection();

            return (Long) cluster.eval("if redis.call(\"get\",KEYS[1]) == ARGV[1] then\n" +
                    "    return redis.call(\"del\",KEYS[1])\n" +
                    "else\n" +
                    "    return 0\n" +
                    "end", Arrays.asList(lockname), Arrays.asList(value.toString()));

        });

        if (result == 1L) {
            log.debug("释放锁成功");
            return true;
        } else {
            log.warn("分布式锁释放失败，key:{}", lockname);
            return false;
        }
    }

    private void initDaemon() {
        executorService.execute(()->{

            while (true) {

                if(!isDaemon){
                    break;
                }
                Thread.currentThread().setName("RedisDistrbuteLockDaemonThread");

                log.info("BaseTime：{}",(timestamp+(EXPIRE_TIME-1)*1000));

                if (System.currentTimeMillis() >= (timestamp+(EXPIRE_TIME-1)*1000)) {
                    //续约
                    redisTemplate.expire(lockname, EXPIRE_TIME, TimeUnit.SECONDS);
                    timestamp=System.currentTimeMillis();

                } else {
                    try {

                        log.info("{}",System.currentTimeMillis());
                        Thread.sleep(sleepTime * 1000);
                    } catch (InterruptedException e) {
                        log.error(e.getMessage());
                    }
                }
            }

        });
    }




}
